package com.motu.monstercity.server.login.verticle;

import com.google.protobuf.InvalidProtocolBufferException;
import com.googlecode.protobuf.format.JsonFormat;
import com.motu.monstercity.module.common.iap.IapProduct;
import com.motu.monstercity.module.common.network.DataDecoded;
import com.motu.monstercity.module.common.network.DataEncoded;
import com.motu.monstercity.module.common.network.ResponseUtils;
import com.motu.monstercity.module.common.redis.RedisKey;
import com.motu.monstercity.module.common.servermanager.ServerInfo;
import com.motu.monstercity.module.common.servermanager.ServerManager;
import com.motu.monstercity.module.common.utility.CmdConstant;
import com.motu.monstercity.module.common.utility.EventBusConstant;
import com.motu.monstercity.module.common.utility.ServerParam;
import com.motu.monstercity.protocol.CmdEnum;
import com.motu.monstercity.protocol.CmdEnum.EnumCmdID;
import com.motu.monstercity.protocol.CommonHttp;
import com.motu.monstercity.protocol.CommonHttp.PBRoleInfo;
import com.motu.monstercity.protocol.CommonHttp.PBServerInfo;
import com.motu.monstercity.protocol.CsHttp.*;
import com.motu.monstercity.server.login.handler.MotumallManager;
import com.motu.monstercity.server.login.iap.LoginIapManager;
import com.motu.monstercity.server.login.notice.NoticeManager;
import com.motu.vertx.module.utility.base.LoginTypeEnum;
import com.motu.vertx.module.utility.base.ServerConfig;
import com.motu.vertx.module.utility.commondata.CommonDataManager;
import com.motu.vertx.module.utility.commondata.UpdateUrl;
import com.motu.vertx.module.utility.login.LoginManager;
import com.motu.vertx.module.utility.main.MainService;
import com.motu.vertx.module.utility.model.EntityManager;
import com.motu.vertx.module.utility.monitor.LoginMonitorManager;
import com.motu.vertx.module.utility.monitor.MonitorManager;
import com.motu.vertx.module.utility.mysql.MysqlClient;
import com.motu.vertx.module.utility.mysql.MysqlTool;
import com.motu.vertx.module.utility.redis.RedisTools;
import com.motu.vertx.module.utility.toolset.ErrorWord;
import com.motu.vertx.module.utility.toolset.SensitivewordEngine;
import com.motu.vertx.module.utility.toolset.TimeUtils;
import com.motu.vertx.module.utility.toolset.Tool;
import io.vertx.core.*;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.redis.client.RedisAPI;
import io.vertx.redis.client.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class HttpVerticle extends AbstractVerticle {

	private static final Logger logger = LoggerFactory.getLogger(HttpVerticle.class);
	public static WebClient webClient;// http客户端

	@Override
	public void start(Promise<Void> future) {

		// 创建http客户端
		webClient = WebClient.create(vertx, new WebClientOptions().setKeepAlive(true));

		// 启动web服务
		startWeb(httpServer -> {
			if (httpServer.failed()) {
				logger.error(" ### startWeb fail !!!");
				httpServer.cause().printStackTrace();
				future.fail(httpServer.cause());
			} else {
				logger.info("----===listen port " + httpServer.result().actualPort());
				future.complete();
			}
		});

	}

	/**
	 * 启动web服务
	 */
	public void startWeb(Handler<AsyncResult<HttpServer>> httpServer) {
		logger.info(">>>>>>>>>>>>>>> startWeb");

		Router router = Router.router(vertx);
		router.route("/").handler(routingContext -> {
			routingContext.response().setStatusCode(200).setStatusMessage("OK").end("Login service is running!!");
		});

		/**
		 * 注意：
		 * 定义了BodyHandler.create之后，只能通过同步方式直接获取body内容，例如context.body().asString()，否则要用routingContext.request().bodyHandler(buffer -> {})获取body内容
		 * 参数设置false是为了解决服务器上生成file-uploads漏洞问题，原因是创建websocket或者http连接时，使用的BodyHandler.create方法默认允许生成上传目录，只要把上传目录的参数置否就可以了
		 */
		router.route().handler(BodyHandler.create(false));

		// 登陆相关逻辑
		router.post("/account").handler(this::doLogic);

		// platform配置和强更包配置
		router.get("/updateurl/create").handler(this::doUpdateurlCreate);

		//检查版本更新
		router.get("/version/check").handler(this::doCheckVersion);
		router.post("/version/check").handler(this::doCheckVersionPost);

		// 客户端充值日志
		router.post("/charge/client/log").handler(this::doChargeClientLog);

		// 客户端下载错误日志
		router.post("/client/downlog").handler(this::doDownLogPost);

		// 二维码跳转
		router.get("/download").handler(this::doQrcodeRedirect);

		// M币商城调用的接口
		router.get("/motumall/*").handler(MotumallManager::doGet);// 魔兔的网页充值
		router.post("/motumall/*").handler(MotumallManager::doPost);// 魔兔的网页充值

		// 启动http服务
		int httpPort = config().getInteger("http-port");// 端口
		String hostName = config().getString("host-name");
		vertx.createHttpServer().requestHandler(router).listen(httpPort, hostName, httpServer::handle);
	}

	/***
	 * 处理登陆相关逻辑
	 * @param context
	 */
	private void doLogic(RoutingContext context) {
		Buffer buffer = context.body().buffer();

		// js的解析格式不同（wechat可以在nginx配置，也可以在代码中配置）
		String isWechat = context.request().getHeader("wechat");
		if (isWechat != null && isWechat.equalsIgnoreCase("true")) {
			JsonObject obj = new JsonObject(buffer);
			Buffer buffer2 = Buffer.buffer();
			for (int i = 0; i < obj.size(); i++) {
				buffer2.appendByte(obj.getInteger("" + i).byteValue());
			}
			buffer = buffer2;
		}

		try {
			DataDecoded reqPacket = new DataDecoded(buffer);
			int cmdCode = reqPacket.getCmdCode();
			EnumCmdID e = CmdEnum.EnumCmdID.forNumber(cmdCode);
			if (e == null) {
				logger.error("doLogic error cmdCode:" + cmdCode);
				sendResponseError(context, ErrorWord.PROTOCOL_PARSE_FAILED);
				return;
			}
//            String cmdName = e.name();
			LoginMonitorManager.addPackageNum();//记录收到的包数
			switch (reqPacket.getCmdCode()) {
				case CmdEnum.EnumCmdID.CMD_CREATE_ROLE_VALUE:
					doCreateRole(context, reqPacket);
					break;
				case CmdEnum.EnumCmdID.CMD_SERVER_LIST_VALUE:
					getServerList(context, reqPacket);
					break;
				case CmdEnum.EnumCmdID.CMD_LOGIN_NEW_VALUE:
					doLoginNew(context, reqPacket);
					break;
				case CmdEnum.EnumCmdID.CMD_GET_NOTICE_VALUE:
					doGetNotice(context, reqPacket);
					break;
				case EnumCmdID.CMD_GET_MAINTAIN_NOTICE_VALUE:
					doGetMaintainNotice(context, reqPacket);
					break;
				case EnumCmdID.CMD_CRASH_LOG_VALUE:
					doCrashLog(context, reqPacket);
					break;
				case EnumCmdID.CMD_CHECK_BADWORD_VALUE:
					checkBadword(context, reqPacket);
					break;
				case EnumCmdID.CMD_CHECK_VERSION_VALUE:
					doCheckVersion(context, reqPacket);
					break;
				case EnumCmdID.CMD_INFORM_VALUE:
					doInform(context, reqPacket);
					break;
				case EnumCmdID.CMD_CHANGE_ACCOUNT_VALUE:
					doChangeAccount(context, reqPacket);
					break;
				case EnumCmdID.CMD_ACCOUNT_BIND_SDK_VALUE:
					doAccountBindSdk(context, reqPacket);
					break;
			}
		} catch (Exception e) {
			logger.error(Tool.getException(e));
			sendResponseError(context, ErrorWord.NET_ERROR_LOGINSERVER_EXCEPTIION);
		}
	}

	private void sendResponseError(RoutingContext routingContext, ErrorWord er) {
		byte data[] = new byte[0];
		Buffer buffer = DataEncoded.encode(0, (byte) er.value, data, 0);
		routingContext.response().putHeader("Content-Type", "application/x-protobuf;charset=utf-8");
		routingContext.response().end(buffer);
	}


	/***
	 * 创建角色
	 * @param context
	 * @param reqPacket 请求数据包
	 */
	private void doCreateRole(RoutingContext context, DataDecoded reqPacket) {
		byte[] reqData = reqPacket.getReqData();
		int language = reqPacket.getLanguage();

		CSCreateRoleResponse.Builder response = CSCreateRoleResponse.newBuilder();
		try {
			CSCreateRoleRequest request = CSCreateRoleRequest.parseFrom(reqData);
			long startTime = System.currentTimeMillis();
			String account = request.getAccount();
			String token = request.getToken();
			int platform = request.getPlatform();
			int requestServerId = request.getServerId();
			String deviceId = request.getDeviceId();
			String ip = Tool.getClientIp(context);
			int accountType = request.getAccountType();
			if (accountType == LoginTypeEnum.MOTU.value) {
				account = account.toLowerCase();//转成小写再入库
			}
			String version = request.getVersion();// 客户端版本号
			boolean simulator = request.getSimulator();// 模拟器

			String requestJson = JsonFormat.printToString(request);
			logger.info("doCreateRole request info = " + requestJson + " ip = " + ip);

			// 判断模拟器能否创角
			if (ServerParam.SIMULATOR_CANNOT_REGISTER == 1 && simulator) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.NOT_DATA_2);
				return;
			}

			// 是否限制某个地区创角（白名单不限制）
			if (!Tool.isEmpty(ServerParam.BAN_REGISTER_REGION)) {
				if (!MonitorManager.isWhiteList(account, ip, platform)) {
					String regionCode = Tool.getIpCountry(context);
					if (Tool.isEmpty(regionCode)) {
						regionCode = CommonDataManager.getIpRegionCode(ip);
					}
					logger.info("doCreateRole regionCode:{} ip:{}", regionCode, ip);
					if (regionCode.equalsIgnoreCase(ServerParam.BAN_REGISTER_REGION)) {
						ResponseUtils.sendError(context, response, reqPacket, ErrorWord.NOT_DATA_1);
						return;
					}
				}
			}

			// 判断是否有注册限制（白名单不限制）
			if (CommonDataManager.isRegisterLimit(platform, request.getChannel(), version, request.getDeviceId())) {
				if (!MonitorManager.isWhiteList(account, ip, platform)) {
					ResponseUtils.sendError(context, response, reqPacket, ErrorWord.NOT_DATA);
					return;
				}
			}

			// 判断是否有封号
			if (MonitorManager.isBanned(account, platform, 0, deviceId, ip)) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.BANNED_DEVICE);
				return;
			}

			ServerInfo serverInfo = ServerManager.getServerInfo(requestServerId);
			if (serverInfo == null) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.BAD_PARAM);
				return;
			}

			int serverId = serverInfo.getServerId();// 使用合服后的服务器ID进行判断，如果合服的服务器范围内有角色，不能再创建

			// 服务器维护
			if (serverInfo.getStatus() == ServerInfo.SERVER_STATUS_MAINTAIN) {
				if (!MonitorManager.isWhiteList(account, ip, platform)) {
					ResponseUtils.sendError(context, response, reqPacket, ErrorWord.BAD_PARAM, serverInfo.getDes(language), null);
					return;
				}
			}

			// 服务器关闭注册
			if (serverInfo.getStatus() == ServerInfo.SERVER_STATUS_CLOSE_REGEDIT) {
				if (!MonitorManager.isWhiteList(account, ip, platform)) {
					ResponseUtils.sendError(context, response, reqPacket, ErrorWord.NOT_DATA_1);
					return;
				}
			}

			// 如果没有配置自增id，则不允许创建角色
			if (ServerParam.TABLE_INIT_ID == 0) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.PERMISSION_DENIED);
				return;
			}

			// ip创角限制
			ErrorWord roleLimitResult = Future.await(checkRegisterIplimit(ip, account, platform, serverId, deviceId));
			if (roleLimitResult.value != ErrorWord.SUCCESS.value) {
				ResponseUtils.sendError(context, response, reqPacket, roleLimitResult, null, ip);
				return;
			}

			// 设备创建限制
			ErrorWord roleDeviceLimitResult = Future.await(checkRegisterDevicelimit(ip, account, platform, serverId, deviceId));
			if (roleDeviceLimitResult.value != ErrorWord.SUCCESS.value) {
				ResponseUtils.sendError(context, response, reqPacket, roleDeviceLimitResult, null, deviceId);
				return;
			}
			// 验证登录token
			String redisKey = RedisKey.getAccountToken(accountType, account);
			JsonObject tokenResult = Future.await(RedisTools.getHashJsonObject(redisKey));
			if (tokenResult.isEmpty() || !tokenResult.getString("token").equalsIgnoreCase(token)) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.TOKEN_WRONG);// token无效
				return;
			}
			int tokenTime = Integer.parseInt(tokenResult.getString("tokentime"));
			if (TimeUtils.getCurTime() - tokenTime > ServerParam.TOKEN_EXPIRED_TIME) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.TOKEN_EXPIRED);// token过期
				return;
			}

			// 账号验证通过，开始创建角色
			String sql = "SELECT 1 FROM user_info JOIN user_ext_info ON user_info.id = user_ext_info.id" +
					" WHERE  user_ext_info.account_id = ? AND user_ext_info.account_type = ? and user_info.server_id = ?" +
					" LIMIT 1;";
			JsonArray params = new JsonArray();
			params.add(account);
			params.add(accountType);
			params.add(serverId);
			JsonArray roleArray = Future.await(MysqlTool.queryJsonArrayFromDb(MysqlClient.getGamePool(), sql, params));
			if (roleArray.size() > 0) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.WRONG_STATUS);// //已经在该服务器上创建过角色了
				return;
			}

			// 创建用户id
			long userId = Future.await(incrTableId("user_info"));
			if (userId < ServerParam.TABLE_INIT_ID) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.PERMISSION_DENIED, userId + "< ServerParam.TABLE_INIT_ID:" + ServerParam.TABLE_INIT_ID, null);
				return;
			}

			// 创建用户信息
			String sqlInsert = "INSERT INTO user_info(id,server_id,user_name,icon,location,language) VALUES(?,?,?,?,?,?);";
			JsonArray paramsInsert = new JsonArray();
			paramsInsert.add(userId);
			paramsInsert.add(serverId);
			paramsInsert.add("");
			paramsInsert.add(0);
			paramsInsert.add(0);
			paramsInsert.add(language);
			Future.await(MysqlTool.updateToDb(MysqlClient.getGamePool(), sqlInsert, paramsInsert));

			PBRoleInfo.Builder sb = PBRoleInfo.newBuilder();
			sb.setUserId(userId);
			sb.setServerId(serverId);
			sb.setRegionTag(ServerParam.MERGE_REGION_TAG);
			response.setRoleInfo(sb);
			response.setRet(0);
			long channel = request.getChannel();
			String sqlInsert2 = "insert into user_ext_info(id,server_id,account_id,account_type,platform,channel,device_id,ip,register_time,mob_id,language) "
					+ "values(?,?,?,?,?,?,?,?,?,?,?);";
			JsonArray paramsInsert2 = new JsonArray();
			paramsInsert2.add(userId);
			paramsInsert2.add(serverId);
			paramsInsert2.add(account);
			paramsInsert2.add(accountType);
			paramsInsert2.add(platform);
			paramsInsert2.add(channel);
			paramsInsert2.add(request.getDeviceId());
			paramsInsert2.add(ip);
			paramsInsert2.add(TimeUtils.getCurTime());
			paramsInsert2.add(request.getMobid());
			paramsInsert2.add(language);

			Future.await(MysqlTool.updateToDb(MysqlClient.getGamePool(), sqlInsert2, paramsInsert2));

			// 把userinfo和userextinfo添加到redis中
			String strUserinfoKey = RedisKey.getUserInfoKey(userId);
			JsonObject objUserinfo = new JsonObject();
			objUserinfo.put("id", userId);
			objUserinfo.put("serverId", serverId);
			objUserinfo.put("userName", "");
			objUserinfo.put("location", 0);
			objUserinfo.put("language", language);
			Future.await(RedisTools.setHashJsonObject(strUserinfoKey, objUserinfo));
			// 标记数据已经初始化
			Future.await(RedisTools.set(EntityManager.getEntityInitKey(strUserinfoKey), "1"));

			String strUserextinfoKey = RedisKey.getUserExtInfoKey(userId);
			JsonObject objUserextinfo = new JsonObject();
			objUserextinfo.put("id", userId);
			objUserextinfo.put("serverId", serverId);
			objUserextinfo.put("accountId", account);
			objUserextinfo.put("platform", platform);
			objUserextinfo.put("channel", channel);
			objUserextinfo.put("deviceId", request.getDeviceId());
			objUserextinfo.put("ip", ip);
			objUserextinfo.put("registerTime", TimeUtils.getCurTime());
			objUserextinfo.put("language", request.getLanguage());
			objUserextinfo.put("accountType", accountType);
			objUserextinfo.put("mobId", request.getMobid());
			Future.await(RedisTools.setHashJsonObject(strUserextinfoKey, objUserextinfo));
			// 标记数据已经初始化
			Future.await(RedisTools.set(EntityManager.getEntityInitKey(strUserextinfoKey), "1"));

			// 成功返回客户端
			ResponseUtils.send(context, response, reqPacket);

			// 记录创角请求数据包信息
			LoginMonitorManager.addCreateRoleData(response.build().toByteArray().length, System.currentTimeMillis() - startTime);

		} catch (Throwable t) {
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
			logger.error("doCreateRole error cause:{}", Tool.getException(t));
		}

	}

	/***
	 * 读取服务器列表
	 * @param context
	 * @param reqPacket 请求数据包
	 */
	private void getServerList(RoutingContext context, DataDecoded reqPacket) {
		int language = reqPacket.getLanguage();
		CSServerListResponse.Builder response = CSServerListResponse.newBuilder();

		try {

			CSServerListRequest request = CSServerListRequest.parseFrom(reqPacket.getReqData());
			String account = request.getAccount();
			String token = request.getToken();
			int accountType = request.getAccountType();

			// 验证登录token
			String redisKey = RedisKey.getAccountToken(accountType, account);
			JsonObject tokenResult = Future.await(RedisTools.getHashJsonObject(redisKey));
			if (tokenResult.isEmpty() || !tokenResult.getString("token").equalsIgnoreCase(token)) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.TOKEN_WRONG);// token无效
				return;
			}

			// 下发完整的服务器列表
			boolean isGm = CommonDataManager.isGmAccount(accountType, account);
			ConcurrentHashMap<Integer, ServerInfo> list = ServerManager.getServerList(isGm, false);
			for (ServerInfo info : list.values()) {
				response.addServerList(getServerInfo(info, language));
			}

			// 此处下发serverId使用user_ext_info.server_id，为玩家注册时的serverId
			String sql = "SELECT user_info.id as id,user_name,icon,vip,level,user_ext_info.server_id as server_id,login_time" +
					" FROM user_info JOIN user_ext_info ON user_info.id = user_ext_info.id" +
					" WHERE user_ext_info.account_id = ? AND user_ext_info.account_type = ?;";
			JsonArray params = new JsonArray();
			params.add(account);
			params.add(accountType);
			JsonArray rlist = Future.await(MysqlTool.queryJsonArrayFromDb(MysqlClient.getGamePool(), sql, params));
			for (int i = 0; i < rlist.size(); i++) {
				response.addRoleList(getRoleInfo(rlist.getJsonObject(i)));
			}
			JsonArray hostedList = Future.await(getHosedUserList(accountType, account));
			if (hostedList != null && !hostedList.isEmpty()) {
				for (int i = 0; i < hostedList.size(); i++) {
					response.addHostedRoleList(getRoleInfo(hostedList.getJsonObject(i)));
				}
			}

			// 下发客户端数据
			ResponseUtils.send(context, response, reqPacket);

		} catch (Throwable t) {
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
			logger.error("getServerList error cause:{}", Tool.getException(t));
		}

	}

	/***
	 * 获取服务器信息pb对象
	 * @param info
	 * @return
	 */
	private PBServerInfo.Builder getServerInfo(ServerInfo info, int language) {
		PBServerInfo.Builder sb = PBServerInfo.newBuilder();
		sb.setId(info.getId());
		sb.setServerId(info.getServerId1());//不能传入integer对象
		sb.setServerStatus(info.getStatus());//不能传入integer对象
		sb.setServerName(info.getServerNameByLanguage(language));
		sb.setWorldChat(info.isWorldChat());
		sb.setServerId2(info.getServerId2());
		sb.setRegionTag(ServerParam.MERGE_REGION_TAG);
		return sb;
	}

	/***
	 * 获取角色pb对象
	 * @param obj
	 * @return
	 */
	private PBRoleInfo.Builder getRoleInfo(JsonObject obj) {
		PBRoleInfo.Builder sb = PBRoleInfo.newBuilder();
		sb.setServerId(obj.getInteger("server_id").intValue());//不能传入integer对象
		sb.setUserId(obj.getLong("id").longValue());//不能传入integer对象
		sb.setUserName(obj.getString("user_name"));
		sb.setIcon(obj.getString("icon"));
		sb.setVip(obj.getInteger("vip").intValue());
		sb.setLevel(obj.getInteger("level").intValue());
		sb.setLoginTime(obj.getInteger("login_time").intValue());
		sb.setRegionTag(ServerParam.MERGE_REGION_TAG);
		return sb;
	}

	/**
	 * 注册ip限制判断
	 */
	private static Future<ErrorWord> checkRegisterIplimit(String ip, String account, int platform, int serverId, String deviceId) {
		Promise<ErrorWord> roleLimitPromise = Promise.promise();
		if (!Tool.isEmpty(ip) && !MonitorManager.isIpWhiteList(ip) && ServerParam.ROLE_NUM_LIMIT > 0) {
			String key = RedisKey.getRoleLimitKey(ip);
			RedisTools.add(key, 1, h -> {
				if (h.succeeded()) {
					long result = h.result();
					if (result == 1) {
						RedisTools.expire(key, ServerParam.ROLE_TIME_LIMIT);
						roleLimitPromise.complete(ErrorWord.SUCCESS);
					} else if (result > ServerParam.ROLE_NUM_LIMIT) {
						try {
							// 超过创建限制数量第一次记录到数据库中
							if (result == ServerParam.ROLE_NUM_LIMIT + 1) {
//									logger.warn("create role limit ip = {}, account = {}, platform = {}, serverId = {}, deviceId = {}, packageName = {}",
//											ip, account2, platform, serverId, deviceId, packageName);
								long curTime = TimeUtils.getCurTime();
								String sql = "insert into iplimitrecord(ip,daytime,account,platform,serverid,deviceid,packagename,num,addtime) values(?,?,?,?,?,?,?,?,?);";
								JsonArray params = new JsonArray();
								params.add(ip);
								params.add(TimeUtils.getStartTimeOfDay(curTime));
								params.add(account);
								params.add(platform);
								params.add(serverId);
								params.add(deviceId);
								params.add("");
								params.add((int) result);
								params.add(curTime);
								MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, res2 -> {
									if (res2.failed()) {
										logger.error("insert iplimitrecord error cause:" + Tool.getException(res2.cause()));
									}
								});
							}
						} catch (Exception e) {
							logger.error(Tool.getException(e));
						}
						roleLimitPromise.complete(ErrorWord.ALREADY_MAX);
					} else {
						roleLimitPromise.complete(ErrorWord.SUCCESS);
					}
				} else {
					roleLimitPromise.complete(ErrorWord.REDIS_QUERY_FAILED);
				}
			});
		} else {
			roleLimitPromise.complete(ErrorWord.SUCCESS);
		}
		return roleLimitPromise.future();
	}

	/**
	 * 注册设备限制判断
	 */
	private static Future<ErrorWord> checkRegisterDevicelimit(String ip, String account, int platform, int serverId, String deviceId) {
		Promise<ErrorWord> roleLimitPromise = Promise.promise();
		if (!Tool.isEmpty(deviceId) && !Tool.isEmpty(ip) && !MonitorManager.isIpWhiteList(ip) && ServerParam.ROLE_DEVICE_NUM_LIMIT > 0) {
			String key = RedisKey.getRoleDeviceLimitKey(deviceId);
			RedisTools.add(key, 1, h -> {
				if (h.succeeded()) {
					long result = h.result();
					if (result == 1) {
						RedisTools.expire(key, ServerParam.ROLE_DEVICE_TIME_LIMIT);
						roleLimitPromise.complete(ErrorWord.SUCCESS);
					} else if (result > ServerParam.ROLE_DEVICE_NUM_LIMIT) {
						try {
							// 超过创建限制数量第一次记录到数据库中
							if (result == ServerParam.ROLE_DEVICE_NUM_LIMIT + 1) {
//									logger.warn("create role limit ip = {}, account = {}, platform = {}, serverId = {}, deviceId = {}, packageName = {}",
//											ip, account2, platform, serverId, deviceId, packageName);
								long curTime = TimeUtils.getCurTime();
								String sql = "insert into iplimitrecord(ip,daytime,account,platform,serverid,deviceid,packagename,num,addtime,type) values(?,?,?,?,?,?,?,?,?,?);";
								JsonArray params = new JsonArray();
								params.add(ip);
								params.add(TimeUtils.getStartTimeOfDay(curTime));
								params.add(account);
								params.add(platform);
								params.add(serverId);
								params.add(deviceId);
								params.add("");
								params.add((int) result);
								params.add(curTime);
								params.add(1);//0-ip 1-设备
								MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, res2 -> {
									if (res2.failed()) {
										logger.error("insert iplimitrecord error cause:" + Tool.getException(res2.cause()));
									}
								});
							}
						} catch (Exception e) {
							logger.error(Tool.getException(e));
						}
						roleLimitPromise.complete(ErrorWord.ALREADY_MAX);
					} else {
						roleLimitPromise.complete(ErrorWord.SUCCESS);
					}
				} else {
					roleLimitPromise.complete(ErrorWord.REDIS_QUERY_FAILED);
				}
			});
		} else {
			roleLimitPromise.complete(ErrorWord.SUCCESS);
		}
		return roleLimitPromise.future();
	}

	private Future<Long> incrTableId(String key) {
		Promise<Long> promise = Promise.promise();
		long initId = ServerParam.TABLE_INIT_ID;
		RedisTools.hsetnx(RedisTools.getTableidKey(), key, String.valueOf(initId), h -> {
			if (h.succeeded()) {
				if (h.result()) {
					logger.info("### table = " + key + " init id = " + initId);
					promise.complete(initId);
				} else {
					RedisTools.incrTableId(key, h2 -> {
						if (h2.succeeded()) {
							promise.complete(h2.result());
						} else {
							promise.fail(h2.cause());
						}
					});
				}
			} else {
				promise.fail(h.cause());
			}
		});
		return promise.future();
	}

	public static void incrTableId(RedisAPI client, String tableName, Handler<AsyncResult<Long>> onComplete) {
		client.hincrby(getTableidKey(), tableName, "1", (res) -> {
			if (res.failed()) {
				logger.error("redis error incrTableId tableName:{} exception:{}", tableName, Tool.getException(res.cause()));
				onComplete.handle(Future.failedFuture(res.cause()));
			} else {
				onComplete.handle(Future.succeededFuture(((Response) res.result()).toLong()));
			}

		});
	}

	public static final String getTableidKey() {
		return "tableid";
	}

	/***
	 * 判断是否为GM帐号，如果是，则返回托管的userid列表
	 * @param accountType
	 * @param account
	 * @return
	 */
	private Future<JsonArray> getHosedUserList(int accountType, String account) {
		Promise<JsonArray> promise = Promise.promise();
		if (CommonDataManager.isGmAccount(accountType, account)) {
			JsonArray ids = CommonDataManager.getHostedUserList(accountType, account);
			if (!ids.isEmpty()) {
				// 此处serverId使用user_ext_info.server_id，为玩家注册时的serverId
				String sql1 = "SELECT user_info.id as id,user_name,icon,vip,level,user_ext_info.server_id as server_id,login_time" +
						" FROM user_info JOIN user_ext_info ON user_info.id = user_ext_info.id" +
						" WHERE user_info.id IN(";
				JsonArray params1 = new JsonArray();
				for (int i = 0; i < ids.size(); i++) {
					if (i != 0) {
						sql1 += ",";
					}
					sql1 += "?";
					params1.add(ids.getLong(i));
				}
				sql1 += ");";
				MysqlTool.queryJsonArrayFromDb(MysqlClient.getGamePool(), sql1, params1, res -> {
					if (res.succeeded()) {
						JsonArray rlist = res.result();
						promise.complete(rlist);
					} else {
						promise.complete(null);
					}
				});

			} else {
				promise.complete(null);
			}
		} else {
			promise.complete(null);
		}
		return promise.future();
	}

	/***
	 * 新登录接口
	 */
	private void doLoginNew(RoutingContext context, DataDecoded reqPacket) {
		int language = reqPacket.getLanguage();
		int cmdCode = reqPacket.getCmdCode();
		String cmdName = CmdEnum.EnumCmdID.forNumber(cmdCode).name();
		byte[] pbdata = reqPacket.getReqData();

		CSLoginNewResponse.Builder response = CSLoginNewResponse.newBuilder();
		try {
			long startTime = System.currentTimeMillis();
			CSLoginNewRequest clientReq = CSLoginNewRequest.parseFrom(reqPacket.getReqData());
			//			Builder res_build1 = res_build1.mergeFrom(pbdata);
//			logger.info("### doLoginNew "+clientReq.toString());
			String account = clientReq.getAccount();
			if (account.trim().length() != account.length()) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.CHAR_NOT_ALLOW);// 包含非法字符
				return;
			}

			String requestJson = JsonFormat.printToString(clientReq);
			String ip = Tool.getClientIp(context);
			logger.info("doLoginNew request info = " + requestJson + " ip = " + ip);

			// 是否限制某个地区登录（白名单不限制）
			if (!Tool.isEmpty(ServerParam.BAN_REGISTER_REGION)) {
				if (!MonitorManager.isWhiteList(account, ip, clientReq.getPlatform())) {
					String regionCode = Tool.getIpCountry(context);
					if (Tool.isEmpty(regionCode)) {
						regionCode = CommonDataManager.getIpRegionCode(ip);
					}
					logger.info("doLoginNew regionCode:{} ip:{} ", regionCode, ip);
					if (regionCode.equalsIgnoreCase(ServerParam.BAN_REGISTER_REGION)) {
						ResponseUtils.sendError(context, response, reqPacket, ErrorWord.NOT_DATA_1);
						return;
					}
				}
			}

			// 判断是否有登陆限制（白名单不限制）
			if (CommonDataManager.isLoginLimit(clientReq.getPlatform(), clientReq.getChannel(), clientReq.getVersion(), clientReq.getDeviceId())) {
				if (!MonitorManager.isWhiteList(account, ip, clientReq.getPlatform())) {
					ResponseUtils.sendError(context, response, reqPacket, ErrorWord.NOT_DATA);
					return;
				}
			}
			// 下发服务器ID扣除值
			if (ServerParam.SERVER_ID_DEDUCT > 0) {
				response.setServerIdDeduct(ServerParam.SERVER_ID_DEDUCT);
			}

			int accountType = clientReq.getAccountType();
			if (accountType == LoginTypeEnum.MOTU.value || accountType == LoginTypeEnum.MOTU_SDK.value) {
				dealLoginMotu(context, clientReq, response, cmdCode, cmdName, pbdata, language, startTime, reqPacket);
			} else {
				dealLoginOther(context, clientReq, response, cmdCode, cmdName, pbdata, language, startTime, reqPacket);
			}
			// 添加新服标记显示时间
			long newServerShowTime = 0;
			long curTime = TimeUtils.getCurTime();
			ConcurrentHashMap<Integer, ServerInfo> list = ServerManager.getServerList(false);
			for (ServerInfo info : list.values()) {
				if (info.getStartTime() > curTime) {
					continue;
				}
				if (info.getStatus() != ServerInfo.SERVER_STATUS_NEW) {
					continue;
				}
				if (info.getStartTime() + ServerParam.NEW_SERVER_SHOW_TIME <= newServerShowTime) {
					continue;
				}
				newServerShowTime = info.getStartTime() + ServerParam.NEW_SERVER_SHOW_TIME;
			}
			response.setNewServerShowTime(newServerShowTime);

		} catch (Throwable t) {
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
			logger.error("doLoginNew error cause:{}", Tool.getException(t));
		}
	}

	/***
	 * 处理魔兔帐号登录
	 * @param context
	 * @param request
	 * @param response
	 * @param cmdCode
	 * @param cmdName
	 * @param language
	 */
	private void dealLoginMotu(RoutingContext context, CSLoginNewRequest request, CSLoginNewResponse.Builder response, int cmdCode, String cmdName, byte[] pbdata, int language, long startTime, DataDecoded reqPacket) {
		String account = request.getAccount();
		String pwd = request.getPassword();
		String guestAccount = request.getGuestaccount();
		String guestPwd = request.getGuestpassword();
		String ip = Tool.getClientIp(context);
		int platform = request.getPlatform();
		long channel = request.getChannel();
		String packageName = request.getPackageName();
		int accountType = request.getAccountType();
		boolean isMotuToken = request.getIsMotuToken();
		String extParam = request.getExtParam();
        //logger.info("### dealLoginMotu type:" + accountType + " account:" + account + " platform:" + platform + " guestAccount:" + guestAccount + " packageName:" + packageName + " isMotuToken:" + isMotuToken + " ip:" + ip);


		Future<JsonObject> futLogin = LoginManager.checkLogin(this.webClient, accountType, account, pwd, extParam, isMotuToken, platform, packageName);
		futLogin.onComplete(resLogin -> {
			JsonObject objLogin = resLogin.result();
			logger.info("### dealLoginMotu objLogin:" + objLogin.toString() + " accountType:" + accountType + " account:" + account);
			int retLogin = objLogin.getInteger("ret");
			int isGuest = 0;
			if (objLogin.containsKey("isguest")) {
				isGuest = objLogin.getInteger("isguest");
			}
			int isGuest2 = isGuest;
			if (retLogin == 0) {//成功
				Future<JsonArray> futRoleList = getRoleList(accountType, account);
				futRoleList.onComplete(resRoleList -> {
					if (resRoleList.succeeded()) {
						JsonArray roleList = resRoleList.result();//如果登录的账号下没角色，就绑定游客角色
						logger.info("### dealLoginMotu roleList:" + roleList.toString() + " accountType:" + accountType + " account:" + account + " isGuest2:" + isGuest2);

						if (roleList.size() == 0 && isGuest2 == 0 && guestAccount != null && !guestAccount.isEmpty() && guestPwd != null && !guestPwd.isEmpty()) {//检测游客帐号
//                            bindGuestAccunt(context, response, pbdata, cmdName, cmdCode, guestAccount, guestPwd, account, platform, channel, packageName, isGuest2, accountType, isMotuToken, language, ip, startTime);
						} else {//取角色列表
							sendLoginNewResult(context, response, cmdName, pbdata, cmdCode, account, platform, channel, packageName, isGuest2, roleList, accountType, ip, language, startTime, reqPacket);
						}
					} else {
						ResponseUtils.sendError(context, response, reqPacket, ErrorWord.MYSQL_QUERY_FAILED);
					}
				});

			} else if (retLogin == ErrorWord.ACCOUNT_NOT_EXIST.value && accountType == LoginManager.LOGIN_TYPE_MOTU) {//帐号不存在
				//注册新帐号
//                Future<JsonObject> futReg = Future.future();
//                LoginManager.regeditMotu(account, pwd, platform, futReg.completer());
//                futReg.onComplete(resReg -> {
//                    JsonObject objReg = resReg.result();
//                    int retReg = objReg.getInteger("ret");
//                    if (retReg != 0) {
//                        sendLoginNewError(context, response, cmdCode, cmdName, pbdata, ErrorWord.getEnum(retReg), language);
//                    } else {
//                        if (guestAccount != null && !guestAccount.isEmpty() && guestPwd != null && !guestPwd.isEmpty()) {//检测游客帐号
//                            bindGuestAccunt(context, response, pbdata, cmdName, cmdCode, guestAccount, guestPwd, account, platform, channel, packageName, isGuest2, accountType, isMotuToken, language, ip, startTime);
//                        } else {//取角色列表
//                            sendLoginNewResult(context, response, cmdName, pbdata, cmdCode, account, platform, channel, packageName, isGuest2, new JsonArray(), accountType, ip, language, startTime);
//                        }
//                    }
//                });
			} else {//其他错误
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.getEnum(retLogin));
			}
		});
	}

	/***
	 * 处理第三方帐号登录
	 * @param context
	 * @param clientReq
	 * @param res_build
	 * @param cmdCode
	 * @param cmdName
	 * @param language
	 */
	private void dealLoginOther(RoutingContext context, CSLoginNewRequest clientReq, CSLoginNewResponse.Builder res_build, int cmdCode, String cmdName, byte[] pbdata, int language, long startTime, DataDecoded reqPacket) {
		String clientAccount = clientReq.getAccount();
		String pwd = clientReq.getPassword();
		String ip = Tool.getClientIp(context);
		int platform = clientReq.getPlatform();
		long channel = clientReq.getChannel();
		String packageName = clientReq.getPackageName();
		int accountType = clientReq.getAccountType();
		boolean isMotuToken = clientReq.getIsMotuToken();
		String extParam = clientReq.getExtParam();
		String guestAccount = clientReq.getGuestaccount();
		String guestPwd = clientReq.getGuestpassword();
//		logger.info("### dealLoginOther type:"+accountType+" account:"+account+" platform:"+platform+" packageName:"+packageName+" isMotuToken:"+isMotuToken+" ip:"+ip);

		// 某些渠道需要先验证token，才能获取到账号
		Promise<String> promiseGetAccount = Promise.promise();
		if (!isMotuToken && accountType == LoginTypeEnum.EFUN.value) {
//            Promise<JsonObject> promiseLogin = Promise.promise();
//            LoginManager.checkLoginEfun(this.webClient, clientAccount, pwd, extParam, packageName, 0, promiseLogin);
//            promiseLogin.future().onComplete(resLogin->{
//                JsonObject objLogin = resLogin.result();
//                int retLogin = objLogin.getInteger("ret");
//                if(retLogin == 0){//成功
//                    JsonObject params = objLogin.getJsonObject("data");
//                    res_build.setExtParam(params.toString());
//                    promiseGetAccount.complete(objLogin.getString("accountid"));
//                }else{//其他错误
//                    promiseGetAccount.fail(String.valueOf(retLogin));
//                }
//            });
		} else {
			promiseGetAccount.complete(clientAccount);
		}

		promiseGetAccount.future().onComplete(resGetAccount -> {
			if (resGetAccount.failed()) {
				ResponseUtils.sendError(context, res_build, reqPacket, ErrorWord.getEnum(Integer.parseInt(resGetAccount.cause().getMessage())));
				return;
			}
			String account = Tool.isEmpty(resGetAccount.result()) ? clientAccount : resGetAccount.result();

			if (Tool.isEmpty(account) && Tool.isEmpty(guestAccount)) {// 游客第一次登陆直接进
				String tempAccount = Tool.getUUID();// 生成游客临时账号
				sendLoginNewResult(context, res_build, cmdName, pbdata, cmdCode, tempAccount, platform, channel, packageName, 1, new JsonArray(), accountType, ip, language, startTime, reqPacket);
			} else {
				Future<JsonArray> futRoleList = getRoleList(accountType, Tool.isEmpty(account) ? guestAccount : account);
				futRoleList.onComplete(resRoleList -> {
					if (resRoleList.succeeded()) {
						JsonArray roleList = resRoleList.result();//如果登录的账号下没角色，就绑定游客角色
//					logger.info("### dealLoginOther roleList:"+roleList.toString()+" accountType:"+accountType+" account:"+account);
						if (Tool.isEmpty(account)) {// 游客直接登录
							sendLoginNewResult(context, res_build, cmdName, pbdata, cmdCode, guestAccount, platform, channel, packageName, 1, roleList, accountType, ip, language, startTime, reqPacket);
						} else if (roleList.size() == 0) {//没角色，跳过验证，直接进
							sendLoginNewResult(context, res_build, cmdName, pbdata, cmdCode, account, platform, channel, packageName, 0, roleList, accountType, ip, language, startTime, reqPacket);
						} else {//到第三方校验
							Future<JsonObject> futLogin = LoginManager.checkLogin(this.webClient, accountType, account, pwd, extParam, isMotuToken, platform, packageName);
							futLogin.onComplete(resLogin -> {
								JsonObject objLogin = resLogin.result();
//							logger.info("### dealLoginOther objLogin:"+objLogin.toString()+" accountType:"+accountType+" account:"+account);
								int retLogin = objLogin.getInteger("ret");
								if (retLogin == 0) {//成功
									sendLoginNewResult(context, res_build, cmdName, pbdata, cmdCode, account, platform, channel, packageName, 0, roleList, accountType, ip, language, startTime, reqPacket);
								} else {//其他错误
									ResponseUtils.sendError(context, res_build, reqPacket, ErrorWord.getEnum(retLogin));
								}
							});
						}
					} else {
						ResponseUtils.sendError(context, res_build, reqPacket, ErrorWord.MYSQL_QUERY_FAILED);
					}
				});
			}
		});
	}

	private Future<JsonArray> getRoleList(int accountType, String account) {
		Promise<JsonArray> promise = Promise.promise();
		String sql;
		JsonArray params = new JsonArray();

		// 登录角色列表，此处下发serverId使用user_ext_info.server_id，为玩家注册时的serverId
		sql = "SELECT user_info.id AS id,user_name,icon,vip,level,user_ext_info.server_id as server_id,login_time" +
				" FROM user_info JOIN user_ext_info ON user_info.id = user_ext_info.id" +
				" WHERE user_ext_info.account_id = ? AND user_ext_info.account_type = ?" +
				" ORDER BY user_info.login_time DESC;";
		params.add(account);
		params.add(accountType);

		MysqlTool.queryJsonArrayFromDb(MysqlClient.getGamePool(), sql, params, res -> {
			if (res.succeeded()) {
				JsonArray rlist = res.result();
				promise.complete(rlist);
			} else {
				String errMsg = res.cause().getMessage();
				logger.error("check login failed:" + errMsg);
				promise.fail(errMsg);
			}

		});
		return promise.future();
	}

	private void sendLoginNewResult(RoutingContext context, CSLoginNewResponse.Builder response, String cmdName,
									byte[] pbdata, int cmdCode, String account, int platform, long channel, String packageName,
									int isGuest, JsonArray rlist, int accountType, String ip, int language, long startTime, DataDecoded reqPacket) {
		String token = Tool.getUUID();
		long tokenTime = TimeUtils.getCurTime();
		JsonObject obj = new JsonObject();
		obj.put("token", token);
		obj.put("tokentime", tokenTime);
		obj.put("isguest", isGuest);
		response.setRet(0);
		response.setToken(token);
		String regionCode = Tool.getIpCountry(context);
		if (regionCode.isEmpty()) {
			regionCode = CommonDataManager.getIpRegionCode(ip);
		}
		response.setRegionCode(regionCode);//地区代码
		if (rlist.size() == 0) {//没角色
			ServerInfo serverInfo = ServerManager.getDefaultServer();
			if (serverInfo != null) {
				response.setServerInfo(getServerInfo(serverInfo, language));
			}
		} else {
			PBRoleInfo.Builder role_build = getRoleInfo(rlist.getJsonObject(0));
			ServerInfo serverInfo = ServerManager.getServerInfo(role_build.getServerId());
			response.setRoleInfo(role_build);
			response.setServerInfo(getServerInfo(serverInfo, language));
		}
		boolean isGm = CommonDataManager.isGmAccount(accountType, account);
		response.setIsGm(isGm);
		response.setIsGuest(isGuest == 1);
		if (isGuest == 1) {
			response.setGuestaccount(account);
		}

		List<IapProduct> iapProductList = LoginIapManager.getIapProductByPackage(packageName);
		for(IapProduct iapProduct : iapProductList) {
			PBIapproduct.Builder pbIapProduct = PBIapproduct.newBuilder();
			pbIapProduct.setIapid(iapProduct.getIapId());
			pbIapProduct.setProductid(iapProduct.getProductId());
			response.addIapProductList(pbIapProduct);
		}
		logger.info("### sendLoginNewResult account = " + account);
		ResponseUtils.send(context, response, reqPacket);

		LoginMonitorManager.addLoginData(response.build().toByteArray().length, System.currentTimeMillis() - startTime);
		String redisKey = RedisKey.getAccountToken(accountType, account);
		RedisTools.setHashJsonObject(redisKey, obj);//更新token和token时间
		RedisTools.expire(redisKey, ServerParam.TOKEN_EXPIRED_TIME);//key的过期时间设置长一点
	}


	/***
	 * 添加强更URL数据到数据库
	 * @param routingContext
	 */
	private void doUpdateurlCreate(RoutingContext routingContext) {
		JsonObject obj = new JsonObject();
		if (!ServerConfig.isDebugServer()) {
			obj.put("code", -1);
			obj.put("errmsg", "非法操作");
			routingContext.response().end(obj.toString());
			return;
		}
		try {
			String packageName = routingContext.request().getParam("packageName");
			int platform = Integer.parseInt(routingContext.request().getParam("platform"));
			long channel = Long.parseLong(routingContext.request().getParam("channel"));
			int version = Integer.parseInt(routingContext.request().getParam("version"));
			int forceVersion = Integer.parseInt(routingContext.request().getParam("forceVersion"));
			String url = routingContext.request().getParam("url");
			int type = Integer.parseInt(routingContext.request().getParam("type"));

			logger.info("doUpdateurlCreate channel:" + channel + " platform:" + platform + " version:" + version + " packageName:" + packageName);

			String selectSql = "select * from update_url where package_name = ? and platform = ? and channel = ?;";
			JsonArray selectParams = new JsonArray();
			selectParams.add(packageName);
			selectParams.add(platform);
			selectParams.add(channel);
			MysqlTool.queryJsonArrayFromDb(MysqlClient.getServerPool(), selectSql, selectParams, handler -> {
				if (handler.succeeded()) {
					JsonArray result = handler.result();
					// 先查询是否存在,版本太低,提示错误,不在更新
					if (result.size() > 0) {
						JsonObject jsonObject = result.getJsonObject(0);
						Integer currentVersion = jsonObject.getInteger("version");
						Integer currentForceVersion = jsonObject.getInteger("force_version");
						if (version < currentVersion || forceVersion < currentForceVersion) {
							obj.put("code", -1);
							obj.put("errmsg", "提示更新版本或强制更新版本比原本低");
							routingContext.response().end(obj.toString());
							return;
						}
					}
					String sql = "REPLACE INTO `update_url` (`package_name`, `platform`, `channel`, `version`, `force_version`, `url`, `type`) VALUES (?, ?, ?, ?, ?, ?, ?);";
					JsonArray params = new JsonArray();

					params.add(packageName).add(platform).add(channel).add(version).add(forceVersion).add(url).add(type);
					MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, handler2 -> {
						if (handler2.succeeded()) {
							// 重载数据各个进程Updateurl数据
							String ebName = EventBusConstant.getAddress(EventBusConstant.GAME_GM);
							EventBus eventBus = MainService.getVertx().eventBus();
							JsonObject message = new JsonObject();
							message.put("cmd", CmdConstant.GM_CMD_RELOAD);
							JsonObject data = new JsonObject()
									.put("method", "CommonDataManager.initUpdateurl")
									.put("path", "com.motu.vertx.module.utility.commondata.")// 修改自己项目的
									.put("servertype", new JsonArray().add(1).add(5).add(6));
							message.put("data", data);
							System.out.println("execute ebName:" + ebName);
							eventBus.publish(ebName, message);//广播所有的进程重载数据

							obj.put("code", 0);
							routingContext.response().end(obj.toString());
						} else {
							obj.put("code", -1);
							obj.put("errmsg", "数据库异常");
							routingContext.response().end(obj.toString());
						}
					});
				} else {
					obj.put("code", -1);
					obj.put("errmsg", "数据库异常");
					routingContext.response().end(obj.toString());
				}
			});
		} catch (Exception e) {
			obj.put("code", -1);
			obj.put("errmsg", "请求解析异常");
			routingContext.response().end(obj.toString());
		}
	}

	/***
	 * 检查版本更新
	 * @param routingContext
	 */
	private void doCheckVersion(RoutingContext routingContext) {
		JsonObject obj = new JsonObject();
		try {
			long channel = Long.parseLong(routingContext.request().getParam("channel"));
			int platform = Integer.parseInt(routingContext.request().getParam("platform"));
			int version = Integer.parseInt(routingContext.request().getParam("version"));
			String packageName = routingContext.request().getParam("packageName");


			logger.info("doCheckVersionGet channel:" + channel + " platform:" + platform + " version:" + version + " packageName:" + packageName);
			UpdateUrl uu = CommonDataManager.getUpdateurl(packageName, platform, channel);
			if (uu != null && version < uu.getVersion()) {
				obj.put("url", uu.getUrl());
				obj.put("type", uu.getType());
				obj.put("forcedUpdate", version < uu.getForceVersion());
				logger.info("doCheckVersionGet uu.getVersion:" + uu.getVersion() + " uu.getForce_version:" + uu.getForceVersion() + " res:" + obj.toString());
			}
			logger.info("doCheckVersionGet resObj:" + obj.toString());
			routingContext.response().end(obj.toString());

		} catch (Throwable t) {
			obj.put("code", -1);
			obj.put("errmsg", "请求解析异常");
			routingContext.response().end(obj.toString());
			logger.error("doCheckVersion Throwable error cause:{}", Tool.getException(t));
		}
	}

	/***
	 * 处理post类型回调
	 * @param routingContext
	 */
	private void doCheckVersionPost(RoutingContext routingContext) {
		JsonObject obj = new JsonObject();
		try {
			JsonObject bodyObj = routingContext.body().asJsonObject();
			logger.info("doCheckVersionPost bodyObj:" + bodyObj.toString());
			long channel = bodyObj.getLong("channel");
			int platform = bodyObj.getInteger("platform");
			int version = bodyObj.getInteger("version");
			String packageName = bodyObj.getString("packageName");
			logger.info("doCheckVersionPost channel:" + channel + " platform:" + platform + " version:" + version);
			UpdateUrl uu = CommonDataManager.getUpdateurl(packageName, platform, channel);
			if (uu != null && version < uu.getVersion()) {
				obj.put("url", uu.getUrl());
				obj.put("type", uu.getType());
				obj.put("forcedUpdate", version < uu.getForceVersion());
				logger.info("doCheckVersionPost uu.getVersion:" + uu.getVersion() + " uu.getForce_version:" + uu.getForceVersion() + " res:" + obj.toString());
			}
			logger.info("doCheckVersionPost resObj:" + obj.toString());
			routingContext.response().end(obj.toString());
		} catch (Throwable t) {
			obj.put("code", -1);
			obj.put("errmsg", "请求解析异常");
			routingContext.response().end(obj.toString());
			logger.error("doCheckVersion Throwable error cause:{}", Tool.getException(t));
		}

	}


	/***
	 * 获取公告
	 */
	private void doGetNotice(RoutingContext context, DataDecoded reqPacket) {
		CSGetNoticeResponse.Builder response = CSGetNoticeResponse.newBuilder();
		int language = reqPacket.getLanguage();
		int cmdCode = reqPacket.getCmdCode();
		String cmdName = CmdEnum.EnumCmdID.forNumber(cmdCode).name();
		byte[] pbdata = reqPacket.getReqData();

		try {
			CSGetNoticeRequest clientReq = CSGetNoticeRequest.parseFrom(reqPacket.getReqData());
			int serverId = clientReq.getServerId();
			int platform = clientReq.getPlatform();
			List<CommonHttp.PBNotice> noticeList = clientReq.getNoticeListList();
			NoticeManager.getNotice(response, serverId, platform, noticeList, language);
			Calendar calendar = new GregorianCalendar();
			response.setTimezone(calendar.getTimeZone().getOffset(System.currentTimeMillis()) / 1000);
			ResponseUtils.send(context, response, reqPacket);
		} catch (InvalidProtocolBufferException e) {
			logger.error("doGetNotice InvalidProtocolBufferException error cause:{}", Tool.getException(e));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.PROTOCOL_PARSE_FAILED);
		} catch (Throwable t) {
			logger.error("doGetNotice Throwable error cause:{}", Tool.getException(t));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
		}
	}

	/**
	 * 获取维护公告
	 */
	private void doGetMaintainNotice(RoutingContext context, DataDecoded reqPacket) {
		CSGetMaintainNoticeResponse.Builder response = CSGetMaintainNoticeResponse.newBuilder();
		int language = reqPacket.getLanguage();
		int cmdCode = reqPacket.getCmdCode();
		String cmdName = CmdEnum.EnumCmdID.forNumber(cmdCode).name();
		byte[] pbdata = reqPacket.getReqData();
		try {
			CSGetMaintainNoticeRequest request = CSGetMaintainNoticeRequest.parseFrom(pbdata);
			int serverId = request.getServerId();
			int platform = request.getPlatform();
			CommonHttp.PBNotice.Builder maintainNotice = NoticeManager.getMaintainNotice(serverId, platform, language);
			if (maintainNotice != null) {
				response.setNotice(maintainNotice);
			}
			ResponseUtils.send(context, response, reqPacket);
		} catch (InvalidProtocolBufferException e) {
			logger.error("doGetMaintainNotice InvalidProtocolBufferException error cause:{}", Tool.getException(e));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.PROTOCOL_PARSE_FAILED);
		} catch (Throwable t) {
			logger.error("doGetMaintainNotice Throwable error cause:{}", Tool.getException(t));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
		}
	}

	/***
	 * 记录客户端崩溃日志
	 */
	private void doCrashLog(RoutingContext context, DataDecoded reqPacket) {
		CSCrashLogResponse.Builder response = CSCrashLogResponse.newBuilder();
		try {
			CSCrashLogRequest clientReq = CSCrashLogRequest.parseFrom(reqPacket.getReqData());
			int serverId = clientReq.getServerId();
			long userId = clientReq.getUserId();
			String username = clientReq.getUserName();
			String content = clientReq.getContent();
			String ver = clientReq.getVer();
			String ip = Tool.getClientIp(context);
			String sql = "INSERT INTO crash_log(server_id,user_id,user_name,content,ver,ip,add_time)  values(?,?,?,?,?,?,?);";
			JsonArray params = new JsonArray();
			params.add(serverId);
			params.add(userId);
			params.add(username);
			params.add(content);
			params.add(ver);
			params.add(ip);
			params.add(TimeUtils.getCurTime());
			MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, handler -> {
				ResponseUtils.send(context, response, reqPacket);
			});
		} catch (Throwable t) {
			logger.error("doCrashLog error cause:{}", Tool.getException(t));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
		}
	}

	/***
	 * 检查是否包含屏蔽字
	 */
	private void checkBadword(RoutingContext context, DataDecoded reqPacket) {
		CSCheckBadwordResponse.Builder response = CSCheckBadwordResponse.newBuilder();
		try {
			CSCheckBadwordRequest clientReq = CSCheckBadwordRequest.parseFrom(reqPacket.getReqData());
			String word = clientReq.getWord();
			if (word == null) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.BAD_PARAM);
				return;
			}
			if (SensitivewordEngine.isContaintSensitiveWord(word, SensitivewordEngine.MinMatchType)) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.BAD_WORD);
			} else {
				ResponseUtils.send(context, response, reqPacket);
			}

		} catch (Throwable t) {
			logger.error("checkBadword error cause:{}", Tool.getException(t));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
		}
	}

	/***
	 * 检查客户端版本号
	 */
	private void doCheckVersion(RoutingContext context, DataDecoded reqPacket) {
		CSCheckVersionResponse.Builder response = CSCheckVersionResponse.newBuilder();
		try {
			CSCheckVersionRequest clientReq = CSCheckVersionRequest.parseFrom(reqPacket.getReqData());
			int platform = clientReq.getPlatform();
			long channel = clientReq.getChannel();
			int version = clientReq.getVersion();
			String packageName = clientReq.getPackageName();
			UpdateUrl uu = CommonDataManager.getUpdateurl(packageName, platform, channel);
			if (uu != null && version < uu.getVersion()) {
				response.setUrl(uu.getUrl());
				response.setType(uu.getType());
			}
			if (uu != null) {
				response.setForcedUpdate(version < uu.getForceVersion());
			}
			response.setRet(0);
			ResponseUtils.send(context, response, reqPacket);
		} catch (Throwable t) {
			logger.error("doCheckVersion error cause:{}", Tool.getException(t));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
		}
	}

	/***
	 * 记录玩家举报
	 */
	private void doInform(RoutingContext context, DataDecoded reqPacket) {
		CSInformResponse.Builder response = CSInformResponse.newBuilder();
		try {
			CSInformRequest clientReq = CSInformRequest.parseFrom(reqPacket.getReqData());
			if (Tool.isEmpty(clientReq.getOpt()) || clientReq.getOpt().indexOf(";") != -1) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.BAD_PARAM);
				return;
			}
			long uid1 = clientReq.getUid1();// 举报人ID
			long uid2 = clientReq.getUid2();// 被举报人ID
			String option = Tool.isEmpty(clientReq.getOpt()) ? "" : clientReq.getOpt();// 举报选项
			String content = Tool.isEmpty(clientReq.getContent()) ? "" : clientReq.getContent();// 举报内容
			String head = Tool.isEmpty(clientReq.getHead()) ? "" : clientReq.getHead();// 举报头像
			// 超过最大长度自动截断
			int maxLength = 200;
			if (content.length() > maxLength) {
				content = content.substring(0, maxLength);
			}
			String chat = Tool.isEmpty(clientReq.getChat()) ? "" : clientReq.getChat();// 聊天内容
			String sql = "insert into inform(uid1,uid2,opt,content,chat,reporthead,time) values(?,?,?,?,?,?,?);";
			JsonArray params = new JsonArray();
			params.add(uid1);
			params.add(uid2);
			params.add(option);
			params.add(content);
			params.add(chat);
			params.add(head);
			params.add(TimeUtils.getCurTime());
			MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, handler -> {
				ResponseUtils.send(context, response, reqPacket);
			});
		} catch (Throwable t) {
			logger.error("doInform error cause:{}", Tool.getException(t));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
		}
	}

	/***
	 * 切换账号
	 */
	private void doChangeAccount(RoutingContext context, DataDecoded reqPacket) {
		CSChangeAccountResponse.Builder response = CSChangeAccountResponse.newBuilder();
		try {
			CSChangeAccountRequest clientReq = CSChangeAccountRequest.parseFrom(reqPacket.getReqData());
			int accountType = clientReq.getAccounttype();// 第三方账号类型
			String account = clientReq.getAccount();
			String extParam = clientReq.getExtParam();

			Future<JsonObject> futLogin = LoginManager.checkLogin(webClient, accountType, account, extParam, extParam, false, 0, "");
			futLogin.onComplete(resLogin -> {
				JsonObject objLogin = resLogin.result();
//							logger.info("### dealLoginOther objLogin:"+objLogin.toString()+" accountType:"+accountType+" account:"+account);
				int retLogin = objLogin.getInteger("ret");
				if (retLogin == 0) {//第三方账号验证成功
					// 生成新token
					String newToken = Tool.getUUID();
					JsonObject obj = new JsonObject();
					obj.put("token", newToken);
					obj.put("tokentime", TimeUtils.getCurTime());
					obj.put("isguest", 0);
					String redisKey = RedisKey.getAccountToken(accountType, account);
					RedisTools.setHashJsonObject(redisKey, obj);//更新token和token时间
					RedisTools.expire(redisKey, ServerParam.TOKEN_EXPIRED_TIME);//key的过期时间设置长一点
					// 返回新token信息
					response.setRet(0);
					response.setToken(newToken);
					ResponseUtils.send(context, response, reqPacket);
				} else {//其他错误
					ResponseUtils.sendError(context, response, reqPacket, ErrorWord.getEnum(retLogin));
				}
			});
		} catch (Throwable t) {
			logger.error("doChangeAccount error cause:{}", Tool.getException(t));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
		}
	}

	/***
	 * sdk游客绑定已有账号（把游客角色的账号ID改成已有账号ID）
	 */
	private void doAccountBindSdk(RoutingContext context, DataDecoded reqPacket) {
		CSAccountBindSdkResponse.Builder response = CSAccountBindSdkResponse.newBuilder();
		try {
			CSAccountBindSdkRequest clientReq = CSAccountBindSdkRequest.parseFrom(reqPacket.getReqData());
			String accountid = clientReq.getAccountid();//游客账号ID
			String bindAccountid = clientReq.getBindAccountid();//绑定的已有账号ID
			String token = clientReq.getToken();//sdk登陆令牌
			int platform = clientReq.getPlatform();//平台号
			MainVerticle.logger.info("doAccountBindSdk accountid = " + accountid + " bindAccountid = " + bindAccountid +
					" token = " + token + " platform = " + platform);

			if (Tool.isEmpty(accountid) || Tool.isEmpty(bindAccountid)) {
				ResponseUtils.sendError(context, response, reqPacket, ErrorWord.BAD_PARAM);
				return;
			}

			//验证SDK登陆令牌
			Future<String> futLogin = LoginManager.checkLoginToken(webClient, accountid, token, platform);
			futLogin.onComplete(resLogin -> {
				if (resLogin.failed()) {
					ResponseUtils.sendError(context, response, reqPacket, ErrorWord.TOKEN_WRONG);
					return;
				}
				//判断两个账号的角色服务器是否有冲突
				Future<JsonArray> future1 = getRoleList(LoginManager.LOGIN_TYPE_MOTU_SDK, accountid);
				future1.onComplete(res1 -> {
					if (res1.failed()) {
						ResponseUtils.sendError(context, response, reqPacket, ErrorWord.MYSQL_QUERY_FAILED);
						return;
					}
					JsonArray roleList1 = res1.result();
					if (roleList1 == null || roleList1.size() == 0) {//游客账号没有角色
						//绑定成功
						String newToken = Tool.getUUID();
						long tokenTime = TimeUtils.getCurTime();
						JsonObject obj = new JsonObject();
						obj.put("token", newToken);
						obj.put("tokentime", tokenTime);
						obj.put("isguest", 0);
						response.setToken(newToken);
						response.setRet(0);
						ResponseUtils.send(context, response, reqPacket);

						//更新token
						String redisKey = RedisKey.getAccountToken(LoginManager.LOGIN_TYPE_MOTU_SDK, bindAccountid);
						RedisTools.setHashJsonObject(redisKey, obj);//更新token和token时间
						RedisTools.expire(redisKey, ServerParam.TOKEN_EXPIRED_TIME);//key的过期时间设置长一点
						return;
					}
					Future<JsonArray> future2 = getRoleList(LoginManager.LOGIN_TYPE_MOTU_SDK, bindAccountid);
					future2.onComplete(res2 -> {
						if (res2.failed()) {
							ResponseUtils.sendError(context, response, reqPacket, ErrorWord.MYSQL_QUERY_FAILED);
							return;
						}
						JsonArray roleList2 = res2.result();
						if (roleList2 != null && roleList2.size() > 0) {
							for (int i = 0; i < roleList1.size(); i++) {
								int serverid1 = roleList1.getJsonObject(i).getInteger("server_id");
								for (int j = 0; j < roleList2.size(); j++) {
									int serverid2 = roleList2.getJsonObject(j).getInteger("server_id");
									if (serverid1 == serverid2) {
										ResponseUtils.sendError(context, response, reqPacket, ErrorWord.INVALID_OPERATION);
										return;
									}
								}
							}
						}
						//角色没有冲突，则把游客角色的账号ID改成已有账号ID
						Future<Integer> updateRoleAccount = updateSdkRoleAccount(roleList1, accountid, bindAccountid);
						updateRoleAccount.onComplete(resUpdateRoleAccount -> {
							if (resUpdateRoleAccount.failed()) {
								ResponseUtils.sendError(context, response, reqPacket, ErrorWord.MYSQL_UPDATE_FAILED);
								return;
							}
							//绑定成功
							String newToken = Tool.getUUID();
							long tokenTime = TimeUtils.getCurTime();
							JsonObject obj = new JsonObject();
							obj.put("token", newToken);
							obj.put("tokentime", tokenTime);
							obj.put("isguest", 0);
							response.setToken(newToken);
							response.setRet(0);
							ResponseUtils.send(context, response, reqPacket);

							//更新token
							String redisKey = RedisKey.getAccountToken(LoginManager.LOGIN_TYPE_MOTU_SDK, bindAccountid);
							RedisTools.setHashJsonObject(redisKey, obj);//更新token和token时间
							RedisTools.expire(redisKey, ServerParam.TOKEN_EXPIRED_TIME);//key的过期时间设置长一点
						});
					});
				});
			});
		} catch (Throwable t) {
			logger.error("doAccountBindSdk error cause:{}", Tool.getException(t));
			ResponseUtils.sendError(context, response, reqPacket, ErrorWord.LOGIC_EXCEPTION);
		}
	}

	// sdk游客角色的账号ID改成已有账号ID
	private Future<Integer> updateSdkRoleAccount(JsonArray roleList, String accountid, String bindAccountid) {
		Promise<Integer> fut = Promise.promise();
		for (int i = 0; i < roleList.size(); i++) {
			JsonObject obj = roleList.getJsonObject(i);
			long id = obj.getLong("id");
			JsonObject jsonObject = new JsonObject();
			jsonObject.put("accountId", bindAccountid);
			jsonObject.put("accountType", LoginManager.LOGIN_TYPE_MOTU_SDK);
			String userExtInfoKey = RedisKey.getUserExtInfoKey(id);
			RedisTools.setHashJsonObject(userExtInfoKey, jsonObject);//更新缓存
		}
		String sql2 = "UPDATE user_ext_info SET account_id = ?, account_type = ? WHERE account_id  = ? AND account_type = ?;";
		JsonArray params2 = new JsonArray();
		params2.add(bindAccountid);
		params2.add(LoginManager.LOGIN_TYPE_MOTU_SDK);
		params2.add(accountid);
		params2.add(LoginManager.LOGIN_TYPE_MOTU_SDK);
		MysqlTool.updateToDb(MysqlClient.getGamePool(), sql2, params2, res2 -> {
			if (res2.succeeded()) {
				fut.complete(0);
			} else {
				fut.complete(ErrorWord.MYSQL_UPDATE_FAILED.value);
			}
		});
		return fut.future();
	}

	/***
	 * 记录客户端充值日志
	 */
	private void doChargeClientLog(RoutingContext routingContext) {
		String request = routingContext.body().asString();
		logger.info("### doChargeClientLog request = " + request);
		// 获取参数
		JsonObject bodyObj = new JsonObject(request.toString());
		long userid = bodyObj.getLong("userid");
		int iapid = bodyObj.getInteger("iapid");
		String orderid = bodyObj.getString("orderid");
		int type = bodyObj.getInteger("type");
		String content = bodyObj.getString("content");

		// 写入数据库
		String sql = "insert into client_charge_log(userid,iapid,orderid,type,addtime,content) values(?,?,?,?,?,?);";
		JsonArray params = new JsonArray();
		params.add(userid);
		params.add(iapid);
		params.add(orderid);
		params.add(type);
		params.add(TimeUtils.getCurTime());
		params.add(content);
		MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, handler -> {
			if (handler.succeeded()) {
				routingContext.response().end("OK");
			} else {
				routingContext.response().end("ERROR");
				logger.error("doChargeClientLog error cause = {}", Tool.getException(handler.cause()));
			}
		});
	}

	/***
	 * 客户端下载错误日志
	 */
	private void doDownLogPost(RoutingContext context) {
		HttpServerRequest request = context.request();
//		request.setExpectMultipart(true);

		String uri = request.absoluteURI();
		logger.info("### doDownLogPost uri = " + uri);
		try {
			String deviceid = request.getParam("deviceid");
			int platform = Integer.parseInt(request.getParam("platform"));
			String version = request.getParam("version");
			String iswifi = request.getParam("iswifi");// true false
			String filename = request.getParam("filename");
			int errcode = Integer.parseInt(request.getParam("errcode"));
			String sql = "insert into downlog(deviceid,platform,version,iswifi,filename,errcode,addtime) values(?,?,?,?,?,?,?);";
			JsonArray params = new JsonArray();
			params.add(deviceid);
			params.add(platform);
			params.add(version);
			params.add(iswifi.equals("true") ? 1 : 0);
			params.add(filename);
			params.add(errcode);
			params.add(TimeUtils.getCurTime());
			MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, res2 -> {
				if (res2.succeeded()) {
					context.response().end("OK");
				} else {
					logger.error(Tool.getException(res2.cause()));
					context.response().end("ERROR");
				}
			});
		} catch (Throwable e) {
			logger.error("### doDownLogPost error uri = " + uri + " cause = " + Tool.getException(e));
		}
	}

	/***
	 * 二维码跳转，根据访问端是ios还是安卓跳转到不同地址
	 * @param routingContext
	 */
	private void doQrcodeRedirect(RoutingContext routingContext) {
		String header = routingContext.request().getHeader("user-agent");
		if (header.contains("Android")) {
			String url = ServerParam.QRCODE_REDIRECT_ANDROID;
			routingContext.response().putHeader("location", url).setStatusCode(302).end();
		} else if (header.contains("iPhone") || header.contains("iPod") || header.contains("iPad")) {
			String url = ServerParam.QRCODE_REDIRECT_IOS;
			routingContext.response().putHeader("location", url).setStatusCode(302).end();
		} else {
			routingContext.response().end(header);
		}
	}

}
